/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.iec.edp.caf.msu.client.register;

import io.iec.edp.caf.commons.runtime.CafEnvironment;
import io.iec.edp.caf.commons.transaction.JpaTransaction;
import io.iec.edp.caf.commons.transaction.TransactionPropagation;
import io.iec.edp.caf.commons.utils.StringUtils;
import io.iec.edp.caf.datasource.CAFDataSourceSelector;
import io.iec.edp.caf.msu.api.ServiceUnitAwareService;
import io.iec.edp.caf.msu.api.client.ServiceRegistry;
import io.iec.edp.caf.msu.api.entity.ServiceUnitInfo;
import io.iec.edp.caf.msu.api.entity.ServiceUnitRegisterInfo;
import io.iec.edp.caf.msu.client.health.DbHealthCheck;
import io.iec.edp.caf.msu.common.domain.entity.GspAppServerEntity;
import io.iec.edp.caf.msu.common.domain.entity.GspSuEntity;
import io.iec.edp.caf.msu.common.domain.repository.AppServerRepository;
import io.iec.edp.caf.msu.common.domain.repository.SuRepository;
import io.iec.edp.caf.msu.common.utils.NetUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.env.Environment;

import javax.annotation.PreDestroy;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.*;

/**
 * 服务中心-服务注册（DB版）
 * 采用数据表GspAppServerEntity和GspSuEntity表作为服务中心
 *
 * @author wangyandong
 * @date 2021/09/10 13:31
 */
@Slf4j
public class DbRegisterImpl implements ServiceRegistry {

    private AppServerRepository appRepo;
    private SuRepository suRepo;
    private ServiceUnitAwareService suAware;

    private boolean enableSSL;
    private List<GspAppServerEntity> appEntities = new ArrayList<>();   //记录实例内部的实例（例如：应用自身 + grpc）
    private Map<String, List<ServiceUnitInfo>> appSuInfoMap = new HashMap<>();

    private DbHealthCheck dbHealthCheck;

    public DbRegisterImpl(AppServerRepository appRepo, SuRepository suRepo, ServiceUnitAwareService suAware) {
        this.appRepo = appRepo;
        this.suRepo = suRepo;
        this.suAware = suAware;
        this.enableSSL = enableSSL();
    }

    @Override
    public Boolean register(ServiceUnitRegisterInfo registerInfo) {
        return register(registerInfo, getLocalIP(), Integer.valueOf(getLocalPort()));
    }

    @Override
    @PreDestroy
    public Boolean unRegister() {
        log.info("ServiceCenter(DataBase) Start to unregister su");

        //遍历appEntities
        for (GspAppServerEntity entity : this.appEntities) {
            String appName = entity.getAppName().toLowerCase();
            //依次注销su、实例信息
            List<String> suNames = this.suAware.getEnabledServiceUnits();
            for (String suName : suNames) {
                this.suRepo.deleteByAppAndSu(appName, suName.toLowerCase());
            }
            this.appRepo.deleteByAppName(appName);

            log.info("ServiceCenter(DataBase) Unregister service [{}]", appName);
        }

        log.info("ServiceCenter(DataBase) Finish unregister su");
        return true;
    }

    @Override
    public Boolean register(ServiceUnitRegisterInfo registerInfo, String ip, Integer port) {
        try {
            //设置主库
            CAFDataSourceSelector.selectMaster();

            JpaTransaction transaction = JpaTransaction.getTransaction();
            try {
                transaction.begin(TransactionPropagation.REQUIRES_NEW);

                //执行注册（DB部署，数据库隔离，namespace不需要）
                doRegister(registerInfo, ip, port.toString());

                transaction.commit();
            } catch (Throwable e) {
                transaction.rollback();
                log.error("ServiceCenter(DataBase) Failed to register su");
                throw new RuntimeException(e);
            }
        } finally {
            //重置数据库信息
            CAFDataSourceSelector.reset();
        }

        return true;
    }

    /**
     * 依次注册实例、su信息
     *
     * @param registerInfo 注册信息
     */
    private void doRegister(ServiceUnitRegisterInfo registerInfo, String ip, String port) {
        String appName = ip + "-" + port;

        log.info("ServiceCenter(DataBase) Start to register su of service [{}]", appName);

        //清理su信息、实例信息
        this.suRepo.deleteByApp(appName);       //此处是一重保险，避免非优雅停机导致数据未清理
        this.appRepo.deleteByAppName(appName);

        //注册实例信息、su信息
        //注册实例信息
        String url = this.enableSSL ? String.format("https://%s:%s", ip, port) : String.format("http://%s:%s", ip, port);
        GspAppServerEntity appEntity = new GspAppServerEntity();
        appEntity.setIp(ip);
        appEntity.setPort(port);
        appEntity.setAppName(appName);
        appEntity.setAppUrl(url);
        //appEntity.setHealthy(true);       暂不使用
        Date currentTimestamp = this.appRepo.getCurrentTimestamp();
        OffsetDateTime dateTime = null;
        if (currentTimestamp != null) {
            currentTimestamp.setTime(currentTimestamp.getTime());
            dateTime = currentTimestamp.toInstant().atZone(ZoneOffset.systemDefault()).toOffsetDateTime();
        }
        appEntity.setBeatTime(dateTime);
        appEntity.setBasePath(CafEnvironment.getBaseUrlPath());
        this.appRepo.save(appEntity);
        //注册su信息
        List<ServiceUnitInfo> suInfos = registerInfo.getServiceUnitInfo();
        for (ServiceUnitInfo su : suInfos) {
            if (su != null) {
                String suName = su.getName().toLowerCase();
                if (this.suRepo.countByAppAndSu(appName, suName) == 0) {
                    GspSuEntity suEntity = new GspSuEntity();
                    suEntity.setId(UUID.randomUUID().toString());
                    suEntity.setApp(appName);
                    suEntity.setSu(suName);
                    this.suRepo.save(suEntity);
                    log.info("ServiceCenter(DataBase) Success to register su [{}]", suName);
                }
            }
        }

        //记录appEntity列表
        if (this.appSuInfoMap.keySet().stream().noneMatch(appName::equals)) {
            this.appEntities.add(appEntity);
            this.appSuInfoMap.put(appName, new ArrayList<>(suInfos));
        } else {
            List<ServiceUnitInfo> infos = this.appSuInfoMap.get(appName);
            for (ServiceUnitInfo info : suInfos) {
                if (infos.stream().noneMatch(x -> x.getName().equalsIgnoreCase(info.getName())))
                    infos.add(info);
            }
        }

        //DB健康检测任务
        if (this.dbHealthCheck != null) {
            this.dbHealthCheck.stop();
        }
        this.dbHealthCheck = new DbHealthCheck(appRepo, suRepo, appEntities, appSuInfoMap);
        this.dbHealthCheck.start();

        log.info("ServiceCenter(DataBase) Finish register su of service [{}]", appName);
    }

    /**
     * 获取当前机器的IP
     */
    private String getLocalIP() {
        //优先获取外网地址
        String ip = NetUtil.INTERNET_IP;
        //取不到外网地址，则返回内网地址
        if (ip == null || "".equals(ip))
            ip = NetUtil.INTRANET_IP;
        return ip;
    }

    /**
     * 获取当前tomcat机器的端口号
     */
    private String getLocalPort() {
        Environment environment = CafEnvironment.getEnvironment();
        return environment.getProperty("local.server.port");
    }

    /**
     * 是否开启https
     */
    private boolean enableSSL() {
        Environment environment = CafEnvironment.getEnvironment();
        return !StringUtils.isEmpty(environment.getProperty("server.ssl.key-store"));
    }
}
